package it.dornauf.java.web2printer;

/**
$Id: $ 
Copyright [2001, 2013] [Oliver Dornauf, pda-systems.COM]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import java.net.URI;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.ResourceBundle;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>
 * Implementation of <strong>Web2Printer</strong> common functions. For thread safety the functions do not modify member
 * variables.
 * </p>
 * 
 * @author <a href="mailto:info@pda-systems.com>Oliver Dornauf</a>
 */

public class Web2Printer
{
    /** token: begin printable content */
    private String startToken;
    /** length of startToken, -1 if no start token is configured */
    private int startTokenLength = -1;
    /** token: end printable content */
    private String endToken;
    /** regex for links */
    private Pattern links;
    /** regex for images */
    private Pattern supressImage;
    /** regex for image alt tag */
    private Pattern altImage;
    /** regex for html body start position */
    private Pattern htmlBodyStart;
    /** regex for html body end position */
    private Pattern htmlBodyEnd;
    /** regex for html header end position */
    private Pattern htmlHeadEnd;
    private Pattern metaCopyright;
    private Pattern metaAuthor;

    public enum ImageProcessing {
        FRAUD, NOTHING, SUPPRESS, REPLACE, REPLACE_WITH_ALT, REPLACE_AND_XREF
    }

    public enum LinkProcessing {
        FRAUD, NOTHING, XREF, CLEAN_XREF
    }

    public enum TagProcessing {
        FRAUD, NOTHING, PRESERVE
    }

    /**
     * <p>
     * ctor, initialise the regex expressions.
     * </p>
     */

    public Web2Printer() {

        links = Pattern.compile("(?i)<a.+href=\"(.+?)\""); //$NON-NLS-1$
        supressImage = Pattern.compile("(?i)<img[^<>]*src=\"(.+?)\"[^<>]*>"); //$NON-NLS-1$
        altImage = Pattern.compile("(i?)alt=\"(.*?)\""); //$NON-NLS-1$
        htmlBodyStart = Pattern.compile("(?i)<body[^<>]*>"); //$NON-NLS-1$
        htmlBodyEnd = Pattern.compile("(?i)</body"); //$NON-NLS-1$
        htmlHeadEnd = Pattern.compile("</head>"); //$NON-NLS-1$
        metaCopyright = Pattern.compile("(?i)<meta([ ])*name=\"copyright\"([ ])*content=\"(.+?)\""); //$NON-NLS-1$
        metaAuthor = Pattern.compile("(?i)<meta([ ])*name=\"author\"([ ])*content=\"(.+?)\""); //$NON-NLS-1$
    }

    /**
     * <p>
     * ctor, initialise a start and a end token. Web2Printer only prints the content between this tokens. If ther is no valid
     * token pair, web2printer prints the hole body.
     * </p>
     * 
     * @param startToken
     * @param endToken
     */

    public Web2Printer(String startToken, String endToken) {
        this();
        this.startToken = startToken;
        this.endToken = endToken;
        startTokenLength = startToken.length();
    }

    /**
     * <p>
     * Extracts the html header from a html page. For web2printer the html header is everything from start to the </head> tag.
     * </p>
     * 
     * @param content
     * @return html feader
     */
    public StringBuffer getHtmlHeader(StringBuffer content) {

        Matcher matcherHeaderEnds = htmlHeadEnd.matcher(content);
        if (matcherHeaderEnds.find()) {
            return new StringBuffer(content.substring(0, matcherHeaderEnds.end()));
        }
        return new StringBuffer(""); //$NON-NLS-1$
    }

    /**
     * <p>
     * Extracts the author and copyright String from meta tags. The returnd String Array hold at index 0 the author and on
     * index 1 the copyright information. Both Elements can be null.
     * </p>
     * 
     * @param content
     * @return StringBuffer array
     */
    public String[] getAuthor(StringBuffer content) {

        String[] retval = new String[2];

        Matcher metaAuthorMatcher = metaAuthor.matcher(content);
        if (metaAuthorMatcher.find()) {
            String author = metaAuthorMatcher.group(3);
            if (author != null) {
                retval[0] = author;
            }
        }

        Matcher metaCopyrightMatcher = metaCopyright.matcher(content);
        if (metaCopyrightMatcher.find()) {
            String copyright = metaCopyrightMatcher.group(3);
            if (copyright != null) {
                retval[1] = copyright;
            }
        }
        return retval;

    }

    /**
     * <p>
     * Extracts the html footer. For web2printer the html footer is everything after the </body> tag.
     * </p>
     * 
     * @param content
     * @return html footer
     */
    public StringBuffer getHtmlFooter(StringBuffer content) {

        Matcher matcherHeaderEnds = htmlBodyEnd.matcher(content);
        if (matcherHeaderEnds.find()) {
            return new StringBuffer(content.substring(matcherHeaderEnds.start()));
        }
        return new StringBuffer(""); //$NON-NLS-1$

    }

    /**
     * <p>
     * Extracts the printable content. The printable content if every thing between the startToken and the endToken. If there
     * is no valid match, web2printer uses everythink between the <body></body> tags.
     * </p>
     * 
     * @param content
     * @return html Header
     */
    public StringBuffer getHtmlBody(StringBuffer content) {

        if (startTokenLength > 0) {
            int start = content.indexOf(startToken);
            int end = content.indexOf(endToken);
            if (end > start) {
                return new StringBuffer("<body>" + content.substring(start, end - 1)); //$NON-NLS-1$
            }
        } else {
            Matcher matcherBodyStart = htmlBodyStart.matcher(content);
            Matcher matcherBodyEnd = htmlBodyEnd.matcher(content);
            if (matcherBodyStart.find() && matcherBodyEnd.find()) {
                return new StringBuffer(content.substring(matcherBodyStart.start(), matcherBodyEnd.start()));

            }
        }
        return content;
    }

    /**
     * <p>
     * Web2Printer image processing. Depending on <code>img</code>
     * </p>
     * <ul>
     * <li>FRAUD : not implemented jet.</li>
     * <li>NOTHING: no processing.</li>
     * <li>XREF: a hyperlink crossreference is generated.</li>
     * <li>CLEAN_XREF: same XREF, but parameters omitted</li>
     * </ul>
     * 
     * @param htmlBody :
     *            content to process
     * @param lnk :
     *            processing type
     * @return processed content
     */

    public StringBuffer processLinks(StringBuffer htmlBody, LinkProcessing lnk, String protocol, String host, int port,
            Vector<String> hyperlinks) {

        HashMap<String, Integer> hyperlinkCache = new HashMap<String, Integer>();
        if (lnk == LinkProcessing.FRAUD || lnk == LinkProcessing.NOTHING) {
            return htmlBody;
        }

        Matcher linkMatcher = links.matcher(htmlBody);
        int xref = 1;
        while (linkMatcher.find()) {
            String currentLink = linkMatcher.group(1);
            URI link = processLink(currentLink, protocol, host, port, hyperlinkCache, hyperlinks);

            if (link != null) {
                if (lnk == LinkProcessing.CLEAN_XREF) {
                    try {
                        link = new URI(link.getScheme(), null, link.getHost(), link.getPort(), link.getPath(), null, null);
                    } catch (URISyntaxException e) {
                    }
                }
                currentLink = link.toString();

            }
            if (!hyperlinkCache.containsKey(currentLink)) {
                hyperlinkCache.put(currentLink, new Integer(xref));
                hyperlinks.add(currentLink);
                ++xref;
            }
            Integer index = hyperlinkCache.get(currentLink);
            // search closing "</a>"
            int pos = htmlBody.indexOf(">", linkMatcher.start()); //$NON-NLS-1$
            pos = htmlBody.indexOf(">", pos + 1); //$NON-NLS-1$
            htmlBody.insert(pos + 1, "<sup>" + index.toString() + "</sup>"); //$NON-NLS-1$ //$NON-NLS-2$
        }
        return htmlBody;
    }

    /**
     * <p>
     * Processing a hyperlink. Converts local links in fully qualified links, removes parameters.
     * </p>
     * 
     * @param link :
     *            hyperlink as string
     * @return URI : fully qualified hyperlink as URI.
     */
    public URI processLink(String link, String protocol, String host, int port, HashMap<String, Integer> hyperlinkCache,
            Vector<String> hyperlinks) {

        URI uri = null;
        try {
            uri = new URI(link);
            String file = uri.getPath();
            if (uri.getHost() == null) {
                if (port > -1) {
                    uri = new URI(protocol, null, host, port, file, null, null);
                } else {
                    uri = new URI(protocol, host, file, null);
                }
            }

        } catch (URISyntaxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return uri;
    }

    /**
     * <p>
     * Web2Printer image processing. Depending on <code>img</code> images processed:
     * </p>
     * <ul>
     * <li>FRAUD : not implemented jet.</li>
     * <li>NOTHING: no processing.</li>
     * <li>REPLACE: images replaces with [IMAGE]</li>
     * <li>REPLACE_WITH_ALT: images replaces with the string from there alt attribute</li>
     * <li>REPLACE_AND_XREF:images replaces with the string from there alt attribute and a crossreference is generated</li>
     * </ul>
     * 
     * @param htmlBody :
     *            content to process
     * @param img :
     *            processing type
     * @return processed content
     */
    public StringBuffer processImages(StringBuffer htmlBody, ImageProcessing img, Vector<String> images, ResourceBundle bundle) {

        HashMap<String, Integer> imageCache = new HashMap<String, Integer>();

        switch (img) {
            case SUPPRESS: {
                Matcher imageMatcher = supressImage.matcher(htmlBody);
                htmlBody = new StringBuffer(imageMatcher.replaceAll(""));
                break;
            }
            case REPLACE: {
                Matcher imageMatcher = supressImage.matcher(htmlBody);
                htmlBody = new StringBuffer(imageMatcher.replaceAll(bundle.getString("Web2Printer.Image"))); 
                break;
            }

            case REPLACE_WITH_ALT:
            case REPLACE_AND_XREF: {
                boolean doXref = (img == ImageProcessing.REPLACE_AND_XREF);
                int xref = 1;
                Matcher imagesMatcher = supressImage.matcher(htmlBody);
                StringBuffer replacement = new StringBuffer();
                String imageAlt = bundle.getString("Web2Printer.ImageAlt");
                while (imagesMatcher.find()) {
                    String imageTag = imagesMatcher.group();
                    String imageSource = imagesMatcher.group(1);
                    Matcher altMatcher = altImage.matcher(imageTag);
                    String altPostfix = ""; //$NON-NLS-1$
                    if (doXref) {
                        if (!imageCache.containsKey(imageSource)) {
                            imageCache.put(imageSource, new Integer(xref));
                            images.add(imageSource);
                            ++xref;
                        }
                        altPostfix = "<sup>" + imageCache.get(imageSource).toString() + "</sup>"; //$NON-NLS-1$ //$NON-NLS-2$
                    }
                    String altTag = ""; //$NON-NLS-1$
                    if (altMatcher.find()) {
                        altTag = altMatcher.group(2);
                    }
                    if (altTag.length() == 0) {
                        altTag = bundle.getString("Web2Printer.Image") + altPostfix;
                    } else {
                        String result = MessageFormat.format(imageAlt, new Object[] { altTag });
                        altTag = result + altPostfix; 
                    }

                    imagesMatcher.appendReplacement(replacement, altTag);
                }
                htmlBody = imagesMatcher.appendTail(replacement);
                break;
            }
        }
        return htmlBody;
    }

    /**
     * <p>
     * Creates the web2printer footer. If the pagUrl is null, unknown is assumed
     * </p>
     * 
     * @param duration
     * @param pageUrl
     * @param hyperlinks
     * @param images
     * @return
     */
    public StringBuffer createFooter(Long duration, String pageUrl, Vector<String> hyperlinks, Vector<String> images,
            String author, String copyright, ResourceBundle bundle) {

        

        String footer1 = bundle.getString("Web2Printer.Footer.1");
        StringBuffer retval = new StringBuffer(MessageFormat.format(footer1, new Object[] { pageUrl }));
        if (author != null)  {
            String authorLabel = bundle.getString("Web2Printer.Footer.2");
            retval.append(MessageFormat.format(authorLabel, new Object[] { author }));
        } 
        if (copyright != null) {
            String copyrightLabel = bundle.getString("Web2Printer.Footer.3");
            retval.append(MessageFormat.format(copyrightLabel, new Object[] { copyright }));
        }

        if (hyperlinks.size() > 0) {
            retval.append(bundle.getString("Web2Printer.Footer.4"));
            int counter = 1;
            for (String link : hyperlinks) {
                retval.append("[" + Integer.toString(counter) + "] <a href=\"" + link + "\">" + link + "</a><br />"); 
                ++counter;
            }
        }
        if (images.size() > 0) {
            retval.append(bundle.getString("Web2Printer.Footer.5")); 
            int counter = 1;
            for (String image : images) {
                retval.append("[" + Integer.toString(counter) + "] <a href=\"" + image + "\">" + image + "</a><br />");
                ++counter;
            }
        }
        String backling = bundle.getString("Web2Printer.Backlink");
        retval.append(MessageFormat.format(backling, new Object[] { duration }));

        return retval;
    }

    /**
     * <p>
     * safely parses the img parameter
     * </p>
     * 
     * @param parameter
     * @return
     */
    public ImageProcessing parseImageParameter(String parameter) {

        ImageProcessing retval = ImageProcessing.NOTHING;
        if (parameter != null) {
            if (parameter.length() == 1) {
                int value = Integer.valueOf(parameter);
                switch (value) {
                    case 0:
                        retval = ImageProcessing.NOTHING;
                        break;
                    case 1:
                        retval = ImageProcessing.SUPPRESS;
                        break;
                    case 2:
                        retval = ImageProcessing.REPLACE;
                        break;
                    case 3:
                        retval = ImageProcessing.REPLACE_WITH_ALT;
                        break;
                    case 4:
                        retval = ImageProcessing.REPLACE_AND_XREF;
                        break;
                    default:
                        retval = ImageProcessing.FRAUD;
                }
            } else {
                return ImageProcessing.FRAUD;
            }
        }
        return retval;
    }

    /**
     * <p>
     * safely parses the lnk parameter
     * </p>
     * 
     * @param parameter
     * @return
     */
    public LinkProcessing parseLinkProcessingParameter(String parameter) {
        LinkProcessing retval = LinkProcessing.NOTHING;
        if (parameter != null) {
            if (parameter.length() == 1) {
                int value = Integer.valueOf(parameter);
                switch (value) {
                    case 0:
                        retval = LinkProcessing.NOTHING;
                        break;
                    case 1:
                        retval = LinkProcessing.XREF;
                        break;
                    case 2:
                        retval = LinkProcessing.CLEAN_XREF;
                        break;
                    default:
                        retval = LinkProcessing.FRAUD;
                }
            } else {
                return LinkProcessing.FRAUD;
            }
        }
        return retval;
    }

    /**
     * <p>
     * safely parses the tag parameter
     * </p>
     * 
     * @param parameter
     * @return
     */
    public TagProcessing parseTagProcessingParameter(String parameter) {
        TagProcessing retval = TagProcessing.NOTHING;
        if (parameter != null) {
            if (parameter.length() == 1) {
                int value = Integer.valueOf(parameter);
                switch (value) {
                    case 0:
                        retval = TagProcessing.NOTHING;
                        break;
                    case 1:
                        retval = TagProcessing.PRESERVE;
                        break;

                    default:
                        retval = TagProcessing.FRAUD;
                }
            } else {
                return TagProcessing.FRAUD;
            }
        }
        return retval;
    }
}